package com.bmit.clusterspring.service;

import com.bmit.clusterspring.log.SaveOuterLogThread;
import com.bmit.clusterspring.mapper.OuterLogMapper;
import com.bmit.clusterspring.model.OuterLog;
import com.bmit.clusterspring.util.RateUtils;
import com.bmit.clusterspring.util.ThreadPoolUtils;
import dataset.CustomizedDataset;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import performance.outer.OuterPerformanceMeasure;
import util.CsvUtils;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;

@Service
public class OutServiceImpl implements OutService {
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Value("${file.raw}")
    private String rawPath;

    @Value("${file.before}")
    private String beforePath;

    @Autowired
    private LogService logService;

    @Autowired
    private OuterLogMapper mapper;

    @Override
    public boolean uploadReference(String ipAddr, MultipartFile file) throws IOException {
        String nowNanoTime = String.valueOf(System.nanoTime());
        String token = nowNanoTime.substring(nowNanoTime.length() - 8);
        String oldRaw = (String) redisTemplate.opsForValue().get(ipAddr + "@raw-reference");
        String fileName = token + "%raw-reference%" + file.getOriginalFilename();
        BufferedOutputStream out = new BufferedOutputStream(
                new FileOutputStream(new File(rawPath + fileName)));
        out.write(file.getBytes());
        out.flush();
        out.close();
        redisTemplate.opsForValue().set(ipAddr + "@raw-reference", fileName);
        System.out.println("raw-reference:" + fileName);
        return true;
    }

    @Override
    public boolean uploadResult(String ipAddr, MultipartFile file) throws IOException {
        String nowNanoTime = String.valueOf(System.nanoTime());
        String token = nowNanoTime.substring(nowNanoTime.length() - 8);
        String oldRaw = (String) redisTemplate.opsForValue().get(ipAddr + "@raw-result");
        String fileName = token + "%raw-result%" + file.getOriginalFilename();
        BufferedOutputStream out = new BufferedOutputStream(
                new FileOutputStream(new File(rawPath + fileName)));
        out.write(file.getBytes());
        out.flush();
        out.close();
        redisTemplate.opsForValue().set(ipAddr + "@raw-result", fileName);
        System.out.println("raw-result:" + fileName);
        return true;
    }

    @Override
    public List<Map<String, String>> showReference(String ipAddr) {
        String raw = (String) redisTemplate.opsForValue().get(ipAddr + "@raw-reference");
        if (raw == null) return null;
        List<Map<String, String>> data = CsvUtils.csvToMap(rawPath + raw, ",");
        data = data.size() < 5 ? data : data.subList(0, 5);
        return data;
    }

    @Override
    public List<Map<String, String>> showResult(String ipAddr) {
        String raw = (String) redisTemplate.opsForValue().get(ipAddr + "@raw-result");
        if (raw == null) return null;
        List<Map<String, String>> data = CsvUtils.csvToMap(rawPath + raw, ",");
        data = data.size() < 5 ? data : data.subList(0, 5);
        return data;
    }

    @Override
    public void setReferenceColumns(String ipAddr, List<String> cols) {
        String fileName = (String) redisTemplate.opsForValue().get(ipAddr + "@raw-reference");
        System.out.println("str:" + fileName);
        String[] split = fileName.split("%");
        String newFileName = split[0] + "%measured%" + split[2];
        CsvUtils.subCsv(rawPath + fileName, beforePath + newFileName, ",", cols);
        deleteFile(rawPath + fileName);
        redisTemplate.opsForValue().set(ipAddr + "@measured", newFileName);
        redisTemplate.opsForValue().set(ipAddr + "@size", String.valueOf(cols.size() - 1));
        System.out.println("str:" + newFileName);
    }

    @Override
    public void setResultColumn(String ipAddr, String col) {
        String fileName = (String) redisTemplate.opsForValue().get(ipAddr + "@raw-result");
        System.out.println("str:" + fileName);
        String[] split = fileName.split("%");
        String newFileName = split[0] + "%result-list%" + split[2];
        List<String> cols = new ArrayList<>();
        cols.add(col);
        CsvUtils.subCsv(rawPath + fileName, beforePath + newFileName, ",", cols);
        deleteFile(rawPath + fileName);
        List<List<String>> list = CsvUtils.csvToList(beforePath + newFileName, ",");
        List<String> results = new ArrayList<>();
        for (List<String> strings : list) {
            results.add(strings.get(0));
        }
        for (String result : results) {
            redisTemplate.opsForList().rightPush(ipAddr + "result-list", result);
        }
    }

    @Override
    public void runMeasure(String ipAddr) {
        String measuredFileName = (String) redisTemplate.opsForValue().get(ipAddr + "@measured");
        System.out.println("run:" + measuredFileName);
        List<List<String>> dataString = CsvUtils.csvToList(beforePath + measuredFileName, ",");
        deleteFile(beforePath + measuredFileName);
        Integer size = Integer.parseInt((String) redisTemplate.opsForValue().get(ipAddr + "@size"));
        CustomizedDataset ds = new CustomizedDataset(dataString, size, 0);
        int[] result = new int[ds.shape[0]];
        for (int i = 0; i < ds.shape[0]; i++) {
            result[i] = Integer.parseInt((String) redisTemplate.opsForList().leftPop(ipAddr + "result-list"));
        }
        OuterPerformanceMeasure outerPerformanceMeasure = new OuterPerformanceMeasure(result, ds);
        Map<String, Double> map = outerPerformanceMeasure.measures();
        redisTemplate.opsForHash().put(ipAddr + "@mere", "FILE_NAME", measuredFileName.split("%")[2]);
        redisTemplate.opsForHash().put(ipAddr + "@mere", "FMI", String.valueOf(map.get("FMI")));
        redisTemplate.opsForHash().put(ipAddr + "@mere", "RI", String.valueOf(map.get("RI")));
        redisTemplate.opsForHash().put(ipAddr + "@mere", "JC", String.valueOf(map.get("JC")));
        redisTemplate.opsForHash().put(ipAddr + "@mere", "COST_TIME", String.valueOf(map.get("COST_TIME")));
        OuterLog log = new OuterLog();
        log.setIpAddress(ipAddr);
        log.setFmi(map.get("FMI"));
        log.setRi(map.get("RI"));
        log.setJc(map.get("JC"));
        log.setRunTime(map.get("COST_TIME"));
        log.setGmtCreate(new Date());
        ThreadPoolUtils.getPool().execute(new SaveOuterLogThread(logService, log));
    }

    @Override
    public Map<String, String> getResult(String ipAddr) {
        Map<String, String> result = new HashMap<>();
        Double fmi = Double.parseDouble((String) redisTemplate.opsForHash().get(ipAddr + "@mere", "FMI"));
        Double ri = Double.parseDouble((String) redisTemplate.opsForHash().get(ipAddr + "@mere", "RI"));
        Double jc = Double.parseDouble((String) redisTemplate.opsForHash().get(ipAddr + "@mere", "JC"));
        List<Double> fmiList = mapper.getFMIs();
        List<Double> riList = mapper.getRIs();
        List<Double> jcList = mapper.getJCs();
        int fmiPosition = RateUtils.insertPosition(fmiList, fmi, RateUtils.DESC);
        int riPosition = RateUtils.insertPosition(riList, ri, RateUtils.DESC);
        int jcPosition = RateUtils.insertPosition(jcList, jc, RateUtils.DESC);
        result.put("FILE_NAME", (String) redisTemplate.opsForHash().get(ipAddr + "@mere", "FILE_NAME"));
        result.put("TOTAL", String.valueOf(fmiList.size()));
        result.put("FMI", String.valueOf(fmi));
        result.put("RI", String.valueOf(ri));
        result.put("JC", String.valueOf(jc));
        result.put("FMI_POS", String.valueOf(fmiPosition));
        result.put("RI_POS", String.valueOf(riPosition));
        result.put("JC_POS", String.valueOf(jcPosition));
        result.put("COST_TIME", (String) redisTemplate.opsForHash().get(ipAddr + "@mere", "COST_TIME"));
        return result;
    }

    public boolean deleteFile(String path) {
        return new File(path).delete();
    }
}
